﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  [Serializable]
  public abstract class ItemKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
  {
    protected ItemKeyedCollection() {
      Contract.Assert(Items != null);
      Contract.Assert(Comparer != null);
    }

    protected ItemKeyedCollection(IEqualityComparer<TKey> comparer)
      : base(comparer) {
      Contract.Assert(Items != null);
      Contract.Assert(Comparer != null);
    }

    protected ItemKeyedCollection(IEnumerable<TItem> items, IEqualityComparer<TKey> comparer = null)
      : this(comparer) {
      Argument.NotNull(items, "items");
      Add(items);
    }

    public new TItem this[TKey key] {
      [DebuggerStepThrough]
      get {
        TItem value;
        if(!TryGetItem(key, out value)) {
          Throw.KeyNotFound(key);
        }//if
        return value;
      }
    }

    [ContractInvariantMethod]
    private void Invariant() {
      Contract.Invariant(Items != null);
      Contract.Invariant(Comparer != null);
      Contract.Invariant(Items.Count == Count);
      Contract.Invariant(Dictionary == null || Dictionary.Count <= Count);
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public void Add(IEnumerable<TItem> items) {
      Argument.NotNull(items, "items");
      Contract.Ensures(Count >= Contract.OldValue(Count));

      foreach(var item in items) {
        Add(item);
      }//for
    }

    [Pure]
    public bool TryGetItem(TKey key, out TItem item) {
      Argument.NotNull(key, "key");

      if(Dictionary != null) {
        return Dictionary.TryGetValue(key, out item);
      }//if

      foreach(var i in Items) {
        var k = GetKeyForItem(i);
        if(k != null && Comparer.Equals(key, k)) {
          item = i;
          return true;
        }//if
      }//for

      item = default(TItem);
      return false;
    }

    [Pure]
    public TItem GetItemOrDefault(TKey key, TItem @default = default(TItem)) {
      Argument.NotNull(key, "key");
      TItem item;
      return TryGetItem(key, out item) ? item : @default;
    }

    [Pure]
    public ItemReadOnlyKeyedCollection<TKey, TItem> AsReadOnly() {
      return new ItemReadOnlyKeyedCollection<TKey, TItem>(this);
    }
  }
}
